package at.jku.pervasive.gui.model;

import java.awt.Color;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.swing.table.AbstractTableModel;

import at.jku.pervasive.Application;
import at.jku.pervasive.Helper;
import at.jku.pervasive.Position;

public class AvailableTagsTableModel extends AbstractTableModel
{

	private final Map<String, Boolean> visibleIds;
	private final Map<String, Color> colors;
	private final Map<String, Long> updateInterval;
	private final List<String> ids;
	private final String[] colums;
	private int rowCount;
	private String filter;

	public AvailableTagsTableModel()
	{
		super();

		this.colums = new String[] { "trace?", "Tag-ID", "last seen", "" };
		this.ids = new LinkedList<String>();
		this.colors = new TreeMap<String, Color>();
		this.visibleIds = new TreeMap<String, Boolean>();
		this.updateInterval = new TreeMap<String, Long>();
	}

	public int getColumnCount()
	{
		return this.colums.length;
	}

	@Override
	public String getColumnName(int column)
	{
		return this.colums[column];
	}

	@Override
	public Class<?> getColumnClass(int columnIndex)
	{
		switch (columnIndex)
		{
			case 0:
				return Boolean.class;
			case 1:
				return String.class;
			case 2:
				return Long.class;
			case 3:
				return Color.class;
		}
		return Object.class;
	}

	public int getRowCount()
	{
		// return this.ids.size();
		return this.rowCount;
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex)
	{
		return (columnIndex == 0) || (columnIndex == 3);
	}

	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex)
	{
		int nextSetBit = getKthSetBit(rowIndex);
		String tagId = this.ids.get(nextSetBit);

		if (columnIndex == 0)
		{
			boolean isTracing = ((Boolean) aValue).booleanValue();
			if (isTracing)
				Application.getInstance().startedTracingTag(tagId);
			else
				Application.getInstance().stopTracingTag(tagId);
		}
		else if (columnIndex == 3)
		{
			Color color = (Color) aValue;
			this.colors.put(tagId, color);

			Application.getInstance().setColorForTagId(tagId, color);
		}
	}

	public Object getValueAt(int rowIndex, int columnIndex)
	{
		// get the index of the rowIndex'th set bit
		int index = getKthSetBit(rowIndex);
		String tagId = this.ids.get(index);

		switch (columnIndex)
		{
			case 0:
				boolean bool = Application.getInstance().isTracingTag(tagId);
				return Boolean.valueOf(bool);
			case 1:
				return this.ids.get(index);
			case 2:
				return this.updateInterval.get(tagId);
			case 3:
				return this.colors.get(tagId);
		}
		return null;
	}

	/**
	 * Get the index of the k'th bit, wich is true
	 * @param k
	 * @return
	 */
	private int getKthSetBit(int k)
	{
		int count = 0;
		int i = 0;
		for (String tagId : this.ids)
		{
			if (this.visibleIds.get(tagId))
			{
				count++;

				if (count == (k + 1))
					return i;
			}
			i++;
		}
		return -1;
	}

	public boolean addTag(String tagId, Color color)
	{
		if (!ids.contains(tagId))
		{
			Color nextColor = Application.getInstance().getNextColor();
			this.ids.add(tagId);
			this.colors.put(tagId, nextColor);
			this.visibleIds.put(tagId, Boolean.TRUE);

			Application.getInstance().setColorForTagId(tagId, nextColor);

			updateRowCount();

			Collections.sort(this.ids);
			
			filterElements();
			fireTableDataChanged();
			
			return true;
		}
		return false;
	}

	private void updateRowCount()
	{
		this.rowCount = 0;
		Set<String> keySet = this.visibleIds.keySet();
		for (String tagId : keySet)
		{
			if (this.visibleIds.get(tagId).booleanValue())
				rowCount++;
		}
	}

	public boolean addTag(String id)
	{
		return addTag(id, Helper.getRandomColor());
	}

	public void clearData()
	{
		this.ids.clear();
		this.colors.clear();
		this.visibleIds.clear();
		this.rowCount = 0;
		fireTableDataChanged();
	}

	public void setFilter(String filterArg)
	{
		this.filter = filterArg;
		filterElements();
	}

	private void filterElements()
	{
		if (filter == null || "".equals(filter))
		{
			// set all to true
			Set<String> tagIds = this.visibleIds.keySet();
			for (String tagId : tagIds)
			{
				this.visibleIds.put(tagId, Boolean.TRUE);
			}
			this.rowCount = tagIds.size();
		}
		else
		{
			// iterate over each tagId and check if the filter applies
			String lowerFilter = filter.toLowerCase();
			String[] filters = lowerFilter.split(" ");

			Set<String> keySet = this.visibleIds.keySet();
			for (String tagId : keySet)
			{
				boolean match = false;
				filterLoop: for (String filterString : filters)
				{
					if (tagId.toLowerCase().contains(filterString))
					{
						match = true;
						break filterLoop;
					}
				}
				this.visibleIds.put(tagId, Boolean.valueOf(match));
			}

			updateRowCount();
		}

		fireTableDataChanged();
	}

	private Map<String, Position> lastPosition = new TreeMap<String, Position>();

	public void updateLastSeenPosition(Position position)
	{
		String tagId = position.getId();
		int row = ids.indexOf(tagId);

		// get last position
		Position lastPos = lastPosition.get(tagId);
		if (lastPos != null)
		{
			long interval = (position.getDate().getTime() - lastPos.getDate().getTime());
			this.updateInterval.put(tagId, Long.valueOf(interval));

			fireTableCellUpdated(row, 2);
		}

		// update last position
		lastPosition.put(tagId, position);
	}
}
